iT邦幫忙

2023 iThome 鐵人賽

DAY 2
1

前言

上一篇我們介紹了資料庫系統利用類似 lock 的方式來實踐transaction,
達到事務隔離(Isolation)的效果,
這裡我們將介紹資料庫系統是如何將那些正在使用的資料上鎖的。

Lock

在我們學習作業系統時,常常會提到為了保護critical section,
讓session內部資料在同一時間內的只有一個thread可以存取。
因為連進入critical section都無法,
所以其他的thread不管是讀或寫都無法達成。

但是,這樣不就會讓所有的thread在critical section外等待了嗎?

該如何優化呢?其實可以多一點通融,
把做寫的thread擋住,
讓只要做讀取的thread進來,
因為讀取操作,也不會影響到critical section內資料的變化。

讀、寫

我們知道了讀與寫的特性,
發現可以偷偷放讀取的thread進入critical section
因此,我們來定義一下什麼時候可以放thread進入critical section。
IO 的操作基本上分為兩類:
不論是對於一筆資料或一個資料表,
以下我們將操作兩兩相互討論。

讀讀不互斥

A transaction 正在 Read 的資料,
同時 B transaction 也想讀取,
就完全將其他 transaction 拒於門外實在有點可惜,
畢竟都是讀取而已,並不會因為 A 讀過 B 讀起來不一樣。
假設當兩者都是只做 Read 指令,我們可以開放讓其他 transaction 讀取,這樣也增加了效率

寫寫互斥

如果 A transaction 正在 Write 的資料,
同時 B transaction 也想 Write 取,我們理互斥,
因為 A transaction 寫完之後必須避免 B transaction 兩者在同一時間裡相互覆蓋,造成 race condition。

讀寫互斥

如果 A transaction 正在 Read 的資料,
同時 B transaction 也想 Write 取,我們理互斥,
因為 A transaction 讀完之後,理應整段 transaction 的資料都不應該有其他變化,直到 A transaction commit 後才可以讓出該筆一資料的使用權。

寫大於讀

剛剛看到了上面三個介紹,可以發現寫的限制大於讀。
如果 A transaction 正在 Read 的資料,讀完之後想要 Write。
理應將原本加在該資料上的讀鎖升級成寫鎖,避免其他 transaction 讀到已經更改過的資料。
所以寫的限制大於讀,讀鎖可以升級寫鎖,反之亦然。

歸納出一個結論

  1. 寫寫互斥 - 寫的過程中不給其他 tx 寫。
  2. 讀寫互斥 - 讀的過程中不給其他 tx 寫。
  3. 讀讀不互斥 - 反正都讀一樣的資料。
  4. 寫大於讀 - 寫的限制大於讀,讀鎖可以升級寫鎖,反之亦然。

所以基本上可以分為兩類鎖:

  1. 讀鎖(共享鎖):讀時可以讓其他 tx 讀,但不可以讓其他 tx
  2. 寫鎖(互斥鎖):寫時不可以讓其他 tx

2PL(Two-Phase Locking)

上面得出在 read/write 時,資料是否可讓其他 transaction read/write的規則,
而該如何在 transaction 的 begin 到 commit/rollback 的過程中是如何上鎖與解鎖的,
這個過程我們使用了 2PL(Two-Phase Locking)
簡而言之就是將 transaction 分為兩個階段:

  • 擴展階段(expanding phase):獲取鎖,並且不允許釋放鎖
  • 收縮階段(shrinking phase):釋放所有鎖,並且無法進一步獲取其他鎖

在擴展階段,我們會將所需要的資料上鎖,
上鎖的規則就如同上面介紹的,分別鎖上讀鎖與寫鎖。
而所有 tx 分別上鎖的所有紀錄將這些鎖記錄在一個 link-list 之中。
可以看到 I 為 resource,T 為 tx。

收縮階段,就可以依照該 tx 所查到的那些 lock,一個一個的釋放,讓其他 tx 可以使用。

上鎖範圍

我們知道了,所基本上分為讀鎖與寫鎖,
代表著該資源使用的權限,是否可讓其他 tx 讀取與覆寫
這裡將介紹上鎖的範圍,有時可能需要鎖上一筆紀錄,有時可能會需要鎖上一張 table。

多粒度鎖 multiple granularity locking (MGL)

這裡介紹到上鎖粒度(lock granularity)

可以看到範圍大到小分別為:
Database>Tables>Pages>Tuples
可以依照需求將不同的粒度的資源上鎖。

補充說明 Page

  • Tuples 就是我們所說的一筆紀錄(record、row)
  • Pages 可以先理解成一些 records,後面的 create 單元將會更詳細解說。

鎖上一筆紀錄 - 鎖(Lock)

上鎖一個 tuples,即上鎖一筆紀錄(record)。
我們通常會這樣表示

  • 讀鎖: S(),Shared lock
  • 寫鎖: X(),Exclusive lock

鎖上一個範圍 - 意向鎖(Intention Lock)

上鎖一個範圍,不論是 table 或 多筆 tuples,我們都會使用到
上面介紹了一個 tuples 的鎖,我們只需要將該 tuples 上鎖即可
但如果是範圍這時候就需要使用意向鎖(Intention Lock)
先舉個例子
txA 將 tableX 做了讀鎖
txB 想要讀取將 tableX 中的某筆 tuples 加上寫鎖,
因 txA 正在讀取,txB 必須等待到 txA 結束後才可以加上寫鎖。
txB 可能要一直從 db 往下找,找到 table,找到 page 最後才找到該筆 tuples,然後發現其實需要等待 txA。
我們是否可以在找到 tuples 之前就是先知道呢?
這時我們引入意向鎖(Intention Lock)的概念,直接在 txA 上鎖的過程中,沿路加上意向鎖,標注我們正在操作該張 table,
當有人想操作該 table 時,看到意向鎖已經在上面了,就不用走到該筆 tuples 後再做等待了。

與 lock 相同,也分讀鎖與寫鎖使用 IS,IX 表示,意向鎖鎖在 tuples 的上層路徑,如同下圖

  • 意向共享鎖:IS(),Intention-shared Lock
  • 意向互斥鎖:IX(),Intention-exclusive Lock

可以看到這張圖,
TxA(藍色):IS(DB),IS(A1),IS(Fa),S(Ran)從 db 到 Table 一路鎖上,直到 tuple 時加上共享鎖
TxB(綠色):X(Fa),當 txB 看到已經有 IS(Fa)了,就不用往下找,知道要先等待 TxA

當 TxA 進入收縮階,將會沿路從 leaf 到 root 的順序釋放 locks

結語

這單元我們知道了 DB 的操作主要為 Read/Write
而 Read/Write 又分別建立了讀鎖與寫鎖的概念
當如果要上鎖不只一筆 tuple,DB 會幫我們加上意向鎖來標注路徑
來加速其他 tx 的使用效率。

這裡我們介紹了 db 的其中一種鎖,他是 db 自帶上的鎖,
並非我們特別撰寫而加上的,因此在 db 界我們稱為隱式鎖
名稱告訴了我們,這裡 db 自己會加上鎖喔,雖然他隱藏著了,但不要忘記了!
而 db 是如何預設加入這些鎖的呢?在下個單元 isolation 會做出更詳細的解說。

這裡我們先介紹悲觀鎖中的隱式鎖,在稍後的單元會再介紹顯式鎖與樂觀鎖。

參考資料

InnoDB 的意向锁有什么作用?
資料庫系統概論


上一篇
[ACID] 事務(Transaction)
下一篇
[ACID]事務隔離(Isolation)
系列文
CRUD仔的一生(上集)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言